import { RepoLink, Link, ReadingRecommendation } from 'libframe-docs/components'

## Overview

Environment: `Browser`.

To achieve <Link text="Client-side Routing" href="/client-routing" noBreadcrumb={true} />, we use `useClientRouter()` (instead of <Link text={<code>getPage()</code>} href="/getPage" />).

```js
// *.page.client.js
// Environment: Browser

import { useClientRouter } from 'vite-plugin-ssr/client/router'

useClientRouter({
  async render(pageContext) {
    if (pageContext.isHydration) {
      // We hydrate the first page
      // ...
    } else {
      // We render a new page (when the user navigates)
      // ...
    }
  }
})
```

> See also: <Link href="/hydration" />

## Examples

React example:
 - <RepoLink path='/examples/react-full/renderer/_default.page.client.tsx' />
 - <RepoLink path='/examples/react-full/pages/index.page.tsx' /> (example of using <code>navigate()</code>)

Vue example:
 - <RepoLink path='/examples/vue-full/renderer/_default.page.client.ts' />
 - <RepoLink path='/examples/vue-full/renderer/app.ts' />
 - <RepoLink path='/examples/vue-full/pages/index.page.vue' /> (example of using <code>navigate()</code>)


## Usage & options

```js
// _default.page.client.js
// Environment: Browser

import { useClientRouter } from 'vite-plugin-ssr/client/router'
import { render, hydrate } from 'some-ui-framework'

const { hydrationPromise } = useClientRouter({
  async render(pageContext) {
    // `pageContext.isHydration` is set by `vite-plugin-ssr` and is `true` when the page
    // is already rendered to HTML.
    if (pageContext.isHydration) {
      // We hydrate the first page. (Since we do SSR, the first page is already
      // rendered to HTML and we merely have to hydrate it.)
      await hydrate(pageContext.Page)
    } else {
      // We render a new page. (When the user navigates to a new page.)
      await render(pageContext.Page)
    }
  },

  // If `ensureHydration: true` then `vite-plugin-ssr` ensures that the first render is always
  // a hydration. (In other words, the hydration process is never interrupted — even if the
  // user clicks on a link before the hydration started. Default value: `false`.)
  // If we use Vue, we need `ensureHydration: true` to avoid "Hydration Mismatch" errors.
  // If we use React, we can leave `ensureHydration: false` for a slight performance improvement.
  ensureHydration: true,

  // See `Link prefetching` section below. Default value: `false`.
  prefetchLinks: true,

  // To create custom page transition animations
  onTransitionStart,
  onTransitionEnd
})

hydrationPromise.then(() => {
  console.log('Hydration finished; page is now interactive.')
})

function onTransitionStart() {
  console.log('Page transition start')
  // For example:
  document.body.classList.add('page-transition')
}
function onTransitionEnd() {
  console.log('Page transition end')
  // For example:
  document.body.classList.remove('page-transition')
}
```

> Note that `pageContext` is completely discarded and created anew upon page navigation.
> (That's why the context object is called `pageContext` &mdash; not `appContext`.)

We can keep using `<a href="/some-url">` links: the Client Router automatically intercepts clicks on `<a>` elements.

We can skip the Client Router by adding the `rel="external"` attribute, e.g. `<a rel="external" href="/some/url">The Client Router won't intercept me</a>`.

We can use
[`navigate('/some/url')`](/navigate)
to programmatically navigate our user to a new page.

By default, the Client-side Router scrolls to the top of the page upon page change;
we can use `<a keep-scroll-position />` / `navigate('/some/url', { keepScrollPosition: true })`
if we want to preserve the scroll position instead. (Useful for [Nested Routes](/nested-routes).)


## State initialization

Usually, when using tools such as Apollo GraphQL, Redux or Vuex, we determine the initial state of our UI on the server-side while rendering HTML, and then initialize the client-side with that initial state.

Depending on the tool, we do either one of the following:
 - We initialize the state once.
 - We re-initialize the state on every page navigation.

<ReadingRecommendation tour={true} links={['/data-fetching', '/data-fetching-tools', '/store']} />

To initialize once:

```js
// _default.page.server.js
// Environment: Node.js

export { render }
export { passToClient }

import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr'
import { renderToHtml } from 'some-ui-framework'
import { getInitialState } from './getInitialState'

const passToClient = ['initialState']

// The `render()` hook is called only for the first page.
// (Whereas `onBeforeRender()` is called as well upon page navigation.)
async function render(pageContext) {
  const initialState = await getInitialState()

  // We use `initialState` for rendering the HTML, so that the HTML contains
  // the content of `initialState`.
  const pageHtml = await renderToHtml(pageContext.Page, initialState)

  const documentHtml = escapeInject`<!DOCTYPE html>
    <html>
      <body>
        <div>${dangerouslySkipEscape(pageHtml)}</div>
      </body>
    </html>`

  return {
    documentHtml,
    pageContext: {
      initialState
    }
  }
}
```
```js
// _default.page.client.js
// Environment: Browser

import { useClientRouter } from 'vite-plugin-ssr/client/router'
import { initClientSide } from './initClientSide'

useClientRouter({
  ensureHydration: true,
  async render(pageContext) {
    // The first page is rendered to HTML and `pageContext.isHydration === true`
    if (pageContext.isHydration) {
      // `pageContext.initialState` is available here
      initClientSide(pageContext.initialState)
      // If we don't set `ensureHydration: true`, then this if-block may never
      // be called. (The first page rendering and the hydration may otherwise
      // be interrupted as explained in the `Usage & Options` section.)
    } else {
      // Note that `pageContext.initialState` is not available here,
      // since our `render()` hook is only called for the first page.
    }

    // ...
  }
})
```

To initialize on every page navigation:

```js
// _default.page.server.js
// Environment: Node.js

export { onBeforeRender }
export { passToClient }

import { getInitialState } from './getInitialState'

const passToClient = ['initialState']

// The `onBeforeRender()` hook is called for the first page as well as upon page navigation.
// (Whereas `render()` is called only for the first page.)
async function onBeforeRender() {
  const initialState = await getInitialState()
  return {
    pageContext: {
      initialState
    }
  }
}
```
```js
// _default.page.client.js
// Environment: Browser

import { useClientRouter } from 'vite-plugin-ssr/client/router'
import { initClientSide } from './initClientSide'

useClientRouter({
  async render(pageContext) {
    // We initialize the state for every page rendering. So not only
    // the first page but also any subsequent page navigation.
    initClientSide(pageContext.initialState)

    // ...
  }
})
```

## Link prefetching

By default,
when the user hovers his mouse over a link `<a href="/some-url">`, the static assets of the page `/some-url` are loaded.
This means that static assets are often already loaded before even the user clicks on the link.

We can prefetch even more eagerly by setting `useClientRouter({ prefetchLinks: true })`: the links are then prefetched as soon as they appear in the user's browser viewport.

We can override the link prefetching behavior for individual links by setting `<a data-prefetch="false" href="/some-url" />`.

> Only the page's static assets are prefetched; the page's `pageContext` is currently not prefetched but it's a work-in-progress feature, see [#246](https://github.com/brillout/vite-plugin-ssr/issues/246).
